// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <algorithm>

#include "base/trace_event/trace_event_argument.h"
#include "base/values.h"
#include "cc/base/math_util.h"
#include "cc/output/filter_operation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/skia_util.h"

namespace cc {

bool FilterOperation::operator==(const FilterOperation& other) const
{
    if (type_ != other.type_)
        return false;
    if (type_ == COLOR_MATRIX)
        return !memcmp(matrix_, other.matrix_, sizeof(matrix_));
    if (type_ == DROP_SHADOW) {
        return amount_ == other.amount_ && drop_shadow_offset_ == other.drop_shadow_offset_ && drop_shadow_color_ == other.drop_shadow_color_;
    }
    if (type_ == REFERENCE) {
        return image_filter_.get() == other.image_filter_.get();
    }
    if (type_ == ALPHA_THRESHOLD) {
        return region_ == other.region_ && amount_ == other.amount_ && outer_threshold_ == other.outer_threshold_;
    }
    return amount_ == other.amount_;
}

FilterOperation::FilterOperation()
    : FilterOperation(GRAYSCALE, 0.f)
{
}

FilterOperation::FilterOperation(FilterType type, float amount)
    : type_(type)
    , amount_(amount)
    , outer_threshold_(0)
    , drop_shadow_offset_(0, 0)
    , drop_shadow_color_(0)
    , zoom_inset_(0)
{
    DCHECK_NE(type_, DROP_SHADOW);
    DCHECK_NE(type_, COLOR_MATRIX);
    DCHECK_NE(type_, REFERENCE);
    memset(matrix_, 0, sizeof(matrix_));
}

FilterOperation::FilterOperation(FilterType type,
    const gfx::Point& offset,
    float stdDeviation,
    SkColor color)
    : type_(type)
    , amount_(stdDeviation)
    , outer_threshold_(0)
    , drop_shadow_offset_(offset)
    , drop_shadow_color_(color)
    , zoom_inset_(0)
{
    DCHECK_EQ(type_, DROP_SHADOW);
    memset(matrix_, 0, sizeof(matrix_));
}

FilterOperation::FilterOperation(FilterType type, SkScalar matrix[20])
    : type_(type)
    , amount_(0)
    , outer_threshold_(0)
    , drop_shadow_offset_(0, 0)
    , drop_shadow_color_(0)
    , zoom_inset_(0)
{
    DCHECK_EQ(type_, COLOR_MATRIX);
    memcpy(matrix_, matrix, sizeof(matrix_));
}

FilterOperation::FilterOperation(FilterType type, float amount, int inset)
    : type_(type)
    , amount_(amount)
    , outer_threshold_(0)
    , drop_shadow_offset_(0, 0)
    , drop_shadow_color_(0)
    , zoom_inset_(inset)
{
    DCHECK_EQ(type_, ZOOM);
    memset(matrix_, 0, sizeof(matrix_));
}

FilterOperation::FilterOperation(FilterType type,
    sk_sp<SkImageFilter> image_filter)
    : type_(type)
    , amount_(0)
    , outer_threshold_(0)
    , drop_shadow_offset_(0, 0)
    , drop_shadow_color_(0)
    , image_filter_(std::move(image_filter))
    , zoom_inset_(0)
{
    DCHECK_EQ(type_, REFERENCE);
    memset(matrix_, 0, sizeof(matrix_));
}

FilterOperation::FilterOperation(FilterType type,
    const SkRegion& region,
    float inner_threshold,
    float outer_threshold)
    : type_(type)
    , amount_(inner_threshold)
    , outer_threshold_(outer_threshold)
    , drop_shadow_offset_(0, 0)
    , drop_shadow_color_(0)
    , zoom_inset_(0)
    , region_(region)
{
    DCHECK_EQ(type_, ALPHA_THRESHOLD);
    memset(matrix_, 0, sizeof(matrix_));
}

FilterOperation::FilterOperation(const FilterOperation& other)
    : type_(other.type_)
    , amount_(other.amount_)
    , outer_threshold_(other.outer_threshold_)
    , drop_shadow_offset_(other.drop_shadow_offset_)
    , drop_shadow_color_(other.drop_shadow_color_)
    , image_filter_(other.image_filter_)
    , zoom_inset_(other.zoom_inset_)
    , region_(other.region_)
{
    memcpy(matrix_, other.matrix_, sizeof(matrix_));
}

FilterOperation::~FilterOperation()
{
}

static FilterOperation CreateNoOpFilter(FilterOperation::FilterType type)
{
    switch (type) {
    case FilterOperation::GRAYSCALE:
        return FilterOperation::CreateGrayscaleFilter(0.f);
    case FilterOperation::SEPIA:
        return FilterOperation::CreateSepiaFilter(0.f);
    case FilterOperation::SATURATE:
        return FilterOperation::CreateSaturateFilter(1.f);
    case FilterOperation::HUE_ROTATE:
        return FilterOperation::CreateHueRotateFilter(0.f);
    case FilterOperation::INVERT:
        return FilterOperation::CreateInvertFilter(0.f);
    case FilterOperation::BRIGHTNESS:
        return FilterOperation::CreateBrightnessFilter(1.f);
    case FilterOperation::CONTRAST:
        return FilterOperation::CreateContrastFilter(1.f);
    case FilterOperation::OPACITY:
        return FilterOperation::CreateOpacityFilter(1.f);
    case FilterOperation::BLUR:
        return FilterOperation::CreateBlurFilter(0.f);
    case FilterOperation::DROP_SHADOW:
        return FilterOperation::CreateDropShadowFilter(
            gfx::Point(0, 0), 0.f, SK_ColorTRANSPARENT);
    case FilterOperation::COLOR_MATRIX: {
        SkScalar matrix[20];
        memset(matrix, 0, 20 * sizeof(SkScalar));
        matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1.f;
        return FilterOperation::CreateColorMatrixFilter(matrix);
    }
    case FilterOperation::ZOOM:
        return FilterOperation::CreateZoomFilter(1.f, 0);
    case FilterOperation::SATURATING_BRIGHTNESS:
        return FilterOperation::CreateSaturatingBrightnessFilter(0.f);
    case FilterOperation::REFERENCE:
        return FilterOperation::CreateReferenceFilter(nullptr);
    case FilterOperation::ALPHA_THRESHOLD:
        return FilterOperation::CreateAlphaThresholdFilter(SkRegion(), 1.f, 0.f);
    }
    NOTREACHED();
    return FilterOperation::CreateEmptyFilter();
}

static float ClampAmountForFilterType(float amount,
    FilterOperation::FilterType type)
{
    switch (type) {
    case FilterOperation::GRAYSCALE:
    case FilterOperation::SEPIA:
    case FilterOperation::INVERT:
    case FilterOperation::OPACITY:
    case FilterOperation::ALPHA_THRESHOLD:
        return MathUtil::ClampToRange(amount, 0.f, 1.f);
    case FilterOperation::SATURATE:
    case FilterOperation::BRIGHTNESS:
    case FilterOperation::CONTRAST:
    case FilterOperation::BLUR:
    case FilterOperation::DROP_SHADOW:
        return std::max(amount, 0.f);
    case FilterOperation::ZOOM:
        return std::max(amount, 1.f);
    case FilterOperation::HUE_ROTATE:
    case FilterOperation::SATURATING_BRIGHTNESS:
        return amount;
    case FilterOperation::COLOR_MATRIX:
    case FilterOperation::REFERENCE:
        NOTREACHED();
        return amount;
    }
    NOTREACHED();
    return amount;
}

// static
FilterOperation FilterOperation::Blend(const FilterOperation* from,
    const FilterOperation* to,
    double progress)
{
    FilterOperation blended_filter = FilterOperation::CreateEmptyFilter();

    if (!from && !to)
        return blended_filter;

    const FilterOperation& from_op = from ? *from : CreateNoOpFilter(to->type());
    const FilterOperation& to_op = to ? *to : CreateNoOpFilter(from->type());

    if (from_op.type() != to_op.type())
        return blended_filter;

    DCHECK(to_op.type() != FilterOperation::COLOR_MATRIX);
    blended_filter.set_type(to_op.type());

    if (to_op.type() == FilterOperation::REFERENCE) {
        if (progress > 0.5)
            blended_filter.set_image_filter(to_op.image_filter());
        else
            blended_filter.set_image_filter(from_op.image_filter());
        return blended_filter;
    }

    blended_filter.set_amount(ClampAmountForFilterType(
        gfx::Tween::FloatValueBetween(progress, from_op.amount(), to_op.amount()),
        to_op.type()));

    if (to_op.type() == FilterOperation::DROP_SHADOW) {
        gfx::Point blended_offset(
            gfx::Tween::LinearIntValueBetween(progress,
                from_op.drop_shadow_offset().x(),
                to_op.drop_shadow_offset().x()),
            gfx::Tween::LinearIntValueBetween(progress,
                from_op.drop_shadow_offset().y(),
                to_op.drop_shadow_offset().y()));
        blended_filter.set_drop_shadow_offset(blended_offset);
        blended_filter.set_drop_shadow_color(gfx::Tween::ColorValueBetween(
            progress, from_op.drop_shadow_color(), to_op.drop_shadow_color()));
    } else if (to_op.type() == FilterOperation::ZOOM) {
        blended_filter.set_zoom_inset(
            std::max(gfx::Tween::LinearIntValueBetween(
                         progress, from_op.zoom_inset(), to_op.zoom_inset()),
                0));
    } else if (to_op.type() == FilterOperation::ALPHA_THRESHOLD) {
        blended_filter.set_outer_threshold(ClampAmountForFilterType(
            gfx::Tween::FloatValueBetween(progress,
                from_op.outer_threshold(),
                to_op.outer_threshold()),
            to_op.type()));
        blended_filter.set_region(to_op.region());
    }

    return blended_filter;
}

void FilterOperation::AsValueInto(base::trace_event::TracedValue* value) const
{
    value->SetInteger("type", type_);
    switch (type_) {
    case FilterOperation::GRAYSCALE:
    case FilterOperation::SEPIA:
    case FilterOperation::SATURATE:
    case FilterOperation::HUE_ROTATE:
    case FilterOperation::INVERT:
    case FilterOperation::BRIGHTNESS:
    case FilterOperation::CONTRAST:
    case FilterOperation::OPACITY:
    case FilterOperation::BLUR:
    case FilterOperation::SATURATING_BRIGHTNESS:
        value->SetDouble("amount", amount_);
        break;
    case FilterOperation::DROP_SHADOW:
        value->SetDouble("std_deviation", amount_);
        MathUtil::AddToTracedValue("offset", drop_shadow_offset_, value);
        value->SetInteger("color", drop_shadow_color_);
        break;
    case FilterOperation::COLOR_MATRIX: {
        value->BeginArray("matrix");
        for (size_t i = 0; i < arraysize(matrix_); ++i)
            value->AppendDouble(matrix_[i]);
        value->EndArray();
        break;
    }
    case FilterOperation::ZOOM:
        value->SetDouble("amount", amount_);
        value->SetDouble("inset", zoom_inset_);
        break;
    case FilterOperation::REFERENCE: {
        int count_inputs = 0;
        if (image_filter_) {
            count_inputs = image_filter_->countInputs();
        }
        value->SetBoolean("is_null", !image_filter_);
        value->SetInteger("count_inputs", count_inputs);
        break;
    }
    case FilterOperation::ALPHA_THRESHOLD: {
        value->SetDouble("inner_threshold", amount_);
        value->SetDouble("outer_threshold", outer_threshold_);
        std::unique_ptr<base::ListValue> region_value(new base::ListValue());
        value->BeginArray("region");
        for (SkRegion::Iterator it(region_); !it.done(); it.next()) {
            value->AppendInteger(it.rect().x());
            value->AppendInteger(it.rect().y());
            value->AppendInteger(it.rect().width());
            value->AppendInteger(it.rect().height());
        }
        value->EndArray();
    } break;
    }
}

namespace {

    SkVector MapStdDeviation(float std_deviation, const SkMatrix& matrix)
    {
        // Corresponds to SpreadForStdDeviation in filter_operations.cc.
        SkVector sigma = SkVector::Make(std_deviation, std_deviation);
        matrix.mapVectors(&sigma, 1);
        return sigma * SkIntToScalar(3);
    }

    gfx::Rect MapRectInternal(const FilterOperation& op,
        const gfx::Rect& rect,
        const SkMatrix& matrix,
        SkImageFilter::MapDirection direction)
    {
        switch (op.type()) {
        case FilterOperation::BLUR: {
            SkVector spread = MapStdDeviation(op.amount(), matrix);
            float spread_x = std::abs(spread.x());
            float spread_y = std::abs(spread.y());
            gfx::RectF result(rect);
            result.Inset(-spread_x, -spread_y, -spread_x, -spread_y);
            return gfx::ToEnclosingRect(result);
        }
        case FilterOperation::DROP_SHADOW: {
            SkVector spread = MapStdDeviation(op.amount(), matrix);
            float spread_x = std::abs(spread.x());
            float spread_y = std::abs(spread.y());
            gfx::RectF result(rect);
            result.Inset(-spread_x, -spread_y, -spread_x, -spread_y);

            gfx::Point drop_shadow_offset = op.drop_shadow_offset();
            SkVector mapped_drop_shadow_offset;
            matrix.mapVector(drop_shadow_offset.x(), drop_shadow_offset.y(),
                &mapped_drop_shadow_offset);
            if (direction == SkImageFilter::kReverse_MapDirection)
                mapped_drop_shadow_offset = -mapped_drop_shadow_offset;
            result += gfx::Vector2dF(mapped_drop_shadow_offset.x(),
                mapped_drop_shadow_offset.y());
            result.Union(gfx::RectF(rect));
            return gfx::ToEnclosingRect(result);
        }
        case FilterOperation::REFERENCE: {
            if (!op.image_filter())
                return rect;
            return gfx::SkIRectToRect(op.image_filter()->filterBounds(
                gfx::RectToSkIRect(rect), matrix, direction));
        }
        default:
            return rect;
        }
    }

} // namespace

gfx::Rect FilterOperation::MapRect(const gfx::Rect& rect,
    const SkMatrix& matrix) const
{
    return MapRectInternal(*this, rect, matrix,
        SkImageFilter::kForward_MapDirection);
}

gfx::Rect FilterOperation::MapRectReverse(const gfx::Rect& rect,
    const SkMatrix& matrix) const
{
    return MapRectInternal(*this, rect, matrix,
        SkImageFilter::kReverse_MapDirection);
}

} // namespace cc
